package com.qd.panda.service.park;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.db.BaseService;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.excel.ExcelExport;
import com.cdqidi.excel.util.ExportPage;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.RedisGeoUtil;
import com.cdqidi.util.RedisTemplateUtil;
import com.cdqidi.util.SpringContextHolder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qd.common.panda.constant.PandaConstant;
import com.qd.common.panda.domain.entity.carpark.*;
import com.qd.common.panda.domain.search.CarParkSearch;
import com.qd.common.sys.constant.Constants;
import com.qd.common.sys.enums.BaseTableEnum;
import com.qd.common.sys.util.SecurityUtils;
import com.qd.panda.mapper.park.CarParkMapper;
import com.qd.panda.model.CarPark;
import com.qd.panda.service.space.ParkSpaceService;
import com.qd.system.service.dict.MbUtil;
import com.qd.system.service.login.LoginUserService;
import com.qd.system.service.org.OrgService;
import com.qd.system.service.system.SystemSetService;
import com.qd.system.service.user.UserOperatorService;
import com.qd.system.util.FileConfirmUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * 停车场信息
 *
 * @author sjk
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class CarParkService extends BaseService<CarParkMapper, CarPark> {
    public static final String PREFIX_ID_AVG = "panda_car_park_avg_pId_{0}";
    public static final String PREFIX_P_ID = "panda_car_park_evaluate_pId_{0}";
    public static final String PREFIX_ID_REDIS = "panda_car_park_redis_pId_{0}";
    private static final String PREFIX_ID = "panda_car_park_pId_{0}";
    private static final String PREFIX_UNIQUE_PANDA_CAR_PARK_CODE_I = "panda_car_park_code_{0}";

    private final RedisTemplateUtil<CarPark> redisTemplateUtil;

    private final OrgService orgService;
    private final LoginUserService loginUserService;
    private final SystemSetService systemSetService;
    private final CarParkFeeService carParkFeeService;
    private final CarParkImgService carParkImgService;
    private final CarParkLocationService carParkLocationService;
    private final CarParkTimeService carParkTimeService;
    private final CarParkLogService carParkLogService;
    private final CarParkChargeService carParkChargeService;
    private final CarParkRevenueService carParkRevenueService;
//    private final ParkSpaceMapper parkSpaceMapper;

    //private final GasStationService gasStationService;
    private final ParkCacheService parkCacheService;

    private final CarParkGasStationService carParkGasStationService;

    private final ObjectMapper objectMapper;
    @Resource
    private HttpServletResponse response;

    /**
     * 保存
     *
     * @param dto 页面对象
     * @return 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public CarParkDTO save(CarParkDTO dto) {
        checkUnique(dto);
        checkMb(dto);
        final CarPark model = dtoToModel(dto);
        try {
            if (super.save(model)) {
                return modelToDto(model);
            }
            throw new ApiException("添加失败");
        } finally {
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 修改
     *
     * @param dto 页面对象
     * @return 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public CarParkDTO update(CarParkDTO dto) {
        checkUnique(dto);
        checkMb(dto);
        final CarPark historyModel = Optional.ofNullable(getById(dto.getPId())).orElseThrow(() -> new ApiException("ID不存在,不能更新"));
        final CarPark model = dtoToModel(dto);
        try {
            if (super.updateById(model)) {
                return modelToDto(model);
            }
            throw new ApiException("更新失败");
        } finally {
            clearCache(historyModel);
            clearCache(model);
            historyModel.freeData();
            model.freeData();
        }
    }

    /**
     * 批量添加
     *
     * @param mList 数据库对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveBatch(List<CarPark> mList) {
        try {
            if (!super.saveBatch(mList)) {
                throw new ApiException("批量添加失败");
            }
        } finally {
            clearCaches(mList);
        }
    }

    /**
     * 批量更新
     *
     * @param mList 数据库对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateBatchIds(List<CarPark> mList) {
        final List<String> ids = mList.stream().map(CarPark::getPId).collect(toList());
        final List<CarPark> historyModelList = checkIds(ids);
        try {
            if (!super.updateBatchById(mList)) {
                throw new ApiException("批量更新失败");
            }
        } finally {
            clearCaches(historyModelList);
            historyModelList.clear();
            ids.clear();
        }
    }

    /**
     * 删除，单个
     *
     * @param pId 主键
     */
    @Transactional(rollbackFor = Exception.class)
    public void removeById(String pId) {
        final CarPark model = Optional.ofNullable(getById(pId)).orElseThrow(() -> new ApiException("数据不存在,不能删除"));
        try {
            if (!super.removeById(pId)) {
                throw new ApiException("删除失败");
            }
        } finally {
            clearAvg(model);
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 根据传入对象构建查询参数，判断是否存在
     *
     * @param dto 参数
     * @return 存在为true
     */
    @Transactional(readOnly = true)
    public Boolean isExists(CarParkDTO dto) {
        final QueryWrapper<CarPark> queryWrapper = createQueryWrapper(dto);
        final boolean exists = super.count(queryWrapper) > 0;
        queryWrapper.clear();
        return exists;
    }

    /**
     * 根据主键判断数据是否存在
     *
     * @param pId 主键
     * @return 存在为true
     */
    @Transactional(readOnly = true)
    public Boolean isExistsById(String pId) {
        boolean exists = false;
        if (StringUtils.hasLength(pId)) {
            final CarPark model = getById(pId);
            exists = (null != model);
            if (exists) {
                model.freeData();
            }
        }
        return exists;
    }

    /**
     * 根据主键获取数据
     *
     * @param pId 主键
     * @return 单条记录
     */
    @Transactional(readOnly = true)
    public CarPark getById(String pId) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, pId);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getById(pId), CarPark.class);
    }

    /**
     * 根据主键获取数据
     *
     * @param pId 主键
     * @return 单条记录，转换成页面对象
     */
    @Transactional(readOnly = true)
    public CarParkDTO getByIdDto(String pId) {
        return Optional.ofNullable(getById(pId)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录
     */
    @Transactional(readOnly = true)
    public CarPark getOne(CarParkDTO dto) {
        final QueryWrapper<CarPark> queryWrapper = createQueryWrapper(dto);
        CarPark model = super.getOne(queryWrapper);
        queryWrapper.clear();
        return model;
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录，转换成页面对象
     */
    @Transactional(readOnly = true)
    public CarParkDTO getOneDto(CarParkDTO dto) {
        return Optional.ofNullable(getOne(dto)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据
     */
    @Transactional(readOnly = true)
    public List<CarPark> getList(CarParkDTO dto) {
        final QueryWrapper<CarPark> queryWrapper = createQueryWrapper(dto);
        final List<CarPark> list = super.list(queryWrapper);
        queryWrapper.clear();
        return list;
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据，转换成页面对象
     */
    @Transactional(readOnly = true)
    public List<CarParkDTO> getListDto(CarParkSearch dto) {
        final QueryWrapper<CarParkSearch> queryWrapper = createQueryWrapper(dto);
        List<CarParkDTO> list = super.baseMapper.list(queryWrapper);
        Optional.ofNullable(list).ifPresent(l -> l.forEach(this::buildDisplay));
        return list;
    }

    /**
     * 分页数据
     *
     * @param page 分页对象
     * @param dto  查询参数
     * @return 分页信息
     */
    @Transactional(readOnly = true)
    public IPage<CarParkDTO> page(Page<CarParkSearch> page, CarParkSearch dto) {
        QueryWrapper<CarParkSearch> queryWrapper = createQueryWrapper(dto);
        UserOperatorService.buildInSql(dto, queryWrapper);
        final IPage<CarParkDTO> iPage = baseMapper.page(page, queryWrapper);
        Optional.ofNullable(iPage.getRecords()).ifPresent(l -> l.forEach(this::buildDisplay));
        dto.freeData();
        return iPage;
    }

    /**
     * 导出Excel
     *
     * @param query 查询条件
     * @throws IOException 异常
     */
    @Transactional(readOnly = true)
    public void export(CarParkSearch query) throws IOException {
        final ExportPage<CarParkSearch, CarParkDTO> exportPage = new ExportPage<>();
        final List<CarParkDTO> list = exportPage.getAllForPage(page -> page(page, query).getRecords());
        try {
            ExcelExport.downloadExcel(response, CarParkDTO.class, () -> list);
        } catch (IOException e) {
            log.error("导出失败", e);
            throw new ApiException("导出失败");
        }
    }

    /**
     * 根据唯一性索引获得数据
     * <p>
     * param code
     *
     * @return 单条数据
     */
    @Transactional(readOnly = true)
    public CarPark getUniqueByCode(String code) {
        final QueryWrapper<CarPark> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("code", code);
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_UNIQUE_PANDA_CAR_PARK_CODE_I, code);
        final CarPark model = redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getOne(queryWrapper, false), CarPark.class);
        queryWrapper.clear();
        return model;
    }

    /**
     * 验证唯一性索引
     *
     * @param dto 页面对象
     */
    @Transactional(readOnly = true)
    public void checkUnique(CarParkDTO dto) {
        CarPark model = getUniqueByCode(dto.getCode());
        if (null != model) {
            checkUnique(dto, model);
        }
    }

    /**
     * 验证唯一性索引
     *
     * @param dto   页面对象
     * @param model 根据约束字段查询出来的数据库对象
     */
    private void checkUnique(CarParkDTO dto, CarPark model) {
        if (!StringUtils.hasLength(dto.getPId())) {
            throw new ApiException("数据已存在，请勿重复添加");
        }
        if (!dto.getPId().equals(model.getPId())) {
            throw new ApiException("数据已存在，请勿重复添加");
        }
    }

    /**
     * 清空缓存
     *
     * @param model 数据库对象
     */
    public void clearCache(CarPark model) {
        if (null == model) {
            return;
        }
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getPId()));
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_P_ID, model.getPId()));
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID_REDIS, model.getPId()));
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_UNIQUE_PANDA_CAR_PARK_CODE_I, model.getCode()));
    }

    public void clearAvg(CarPark model) {
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID_AVG, model.getPId()));
    }

    private void clearCaches(List<CarPark> mList) {
        for (CarPark carPark : mList) {
            clearCache(carPark);
            clearAvg(carPark);
        }
    }

    /**
     * 数据库对象转页面对象
     *
     * @param model 数据库对象
     * @return 页面对象
     */
    public CarParkDTO modelToDto(CarPark model) {
        if (null == model) {
            return null;
        }
        final CarParkDTO dto = new CarParkDTO();
        dto.setPId(model.getPId());
        dto.setCode(model.getCode());
        dto.setName(model.getName());
        dto.setIntroduce(model.getIntroduce());
        dto.setParkNum(model.getParkNum());
        dto.setIntoFreeTime(model.getIntoFreeTime());
        dto.setPayFreeTime(model.getPayFreeTime());
        dto.setQrCode(model.getQrCode());
        dto.setCarParkType(model.getCarParkType());
        dto.setAddTime(model.getAddTime());
        dto.setUpdateTime(model.getUpdateTime());
        dto.setConfirmIn(model.getConfirmIn());
        dto.setThirdPartyBill(model.getThirdPartyBill());
        dto.setConfirmOut(model.getConfirmOut());
        dto.setOperator(model.getOperator());
        dto.setOrgId(model.getOrgId());
        dto.setEnable(model.getEnable());
        dto.setOwnBusiness(model.getOwnBusiness());
        dto.setParkType(model.getParkType());
        dto.setOnStreet(model.getOnStreet());
        dto.setParkSpaceType(model.getParkSpaceType());
        dto.setEtcPay(model.getEtcPay());
        dto.setContactNumber(model.getContactNumber());
        dto.setShareRatio(model.getShareRatio());
        dto.setDefaultAmount(model.getDefaultAmount());
        dto.setGateBrand(model.getGateBrand());
        buildDisplay(dto);
        return dto;
    }

    /**
     * 页面对象转数据库对象
     *
     * @param dto 页面对象
     * @return 数据库对象
     */
    public CarPark dtoToModel(CarParkDTO dto) {
        if (null == dto) {
            return null;
        }
        final CarPark model = new CarPark();
        model.setPId(dto.getPId());
        model.setCode(dto.getCode());
        model.setName(dto.getName());
        model.setIntroduce(dto.getIntroduce());
        model.setParkNum(dto.getParkNum());
        model.setIntoFreeTime(dto.getIntoFreeTime());
        model.setPayFreeTime(dto.getPayFreeTime());
        model.setQrCode(dto.getQrCode());
        model.setCarParkType(dto.getCarParkType());
        model.setAddTime(dto.getAddTime());
        model.setUpdateTime(dto.getUpdateTime());
        model.setConfirmIn(dto.getConfirmIn());
        model.setThirdPartyBill(dto.getThirdPartyBill());
        model.setConfirmOut(dto.getConfirmOut());
        model.setOperator(dto.getOperator());
        model.setOrgId(dto.getOrgId());
        model.setEnable(dto.getEnable());
        model.setOwnBusiness(dto.getOwnBusiness());
        model.setParkType(dto.getParkType());
        model.setOnStreet(dto.getOnStreet());
        model.setParkSpaceType(dto.getParkSpaceType());
        model.setEtcPay(dto.getEtcPay());
        model.setContactNumber(dto.getContactNumber());
        model.setShareRatio(dto.getShareRatio());
        model.setDefaultAmount(dto.getDefaultAmount());
        model.setGateBrand(dto.getGateBrand());
        return model;
    }

    /**
     * 码表转换
     *
     * @param dto 页面对象
     */
    public void buildDisplay(CarParkDTO dto) {
        dto.setConfirmInDisplay(MbUtil.idToDisplay(dto.getConfirmIn()));
        dto.setCarParkTypeDisplay(MbUtil.idToDisplay(dto.getCarParkType()));
        dto.setThirdPartyBillDisplay(MbUtil.idToDisplay(dto.getThirdPartyBill()));
        dto.setConfirmOutDisplay(MbUtil.idToDisplay(dto.getConfirmOut()));
        dto.setOnStreetDisplay(MbUtil.idToDisplay(dto.getOnStreet()));
        dto.setParkSpaceTypeDisplay(MbUtil.idToDisplay(dto.getParkSpaceType()));
        dto.setOperatorDisplay(loginUserService.idToDisplay(dto.getOperator()));
        dto.setEtcPayDisplay(MbUtil.idToDisplay(dto.getEtcPay()));
        dto.setGateBrandDisplay(MbUtil.idToDisplay(dto.getGateBrand()));
        if (!StringUtils.hasLength(dto.getOrgIdDisplay())) {
            dto.setOrgIdDisplay(orgService.idToDisplay(dto.getOrgId()));
        }
        dto.setEnableDisplay(MbUtil.idToDisplay(dto.getEnable()));
        dto.setParkTypeDisplay(MbUtil.idToDisplay(dto.getParkType()));
        dto.setOwnBusinessDisplay(MbUtil.idToDisplay(dto.getOwnBusiness()));
        if (StringUtils.hasLength(dto.getQrCode())) {
            dto.setQrCodeDisplay(FileConfirmUtil.getFilePath(dto.getQrCode(), dto.getPId() + ".jpg"));
        }
        dto.setFee(Optional.ofNullable(carParkFeeService.getUniqueByPId(dto.getPId())).map(carParkFeeService::modelToDto).orElse(null));
        dto.setAppointmentSet(parkCacheService.getCarParkAppointmentSetService().getByIdDto(dto.getPId()));
        dto.setIsAppointmentSet(null == dto.getAppointmentSet() ? ConstantDto.SF0 : ConstantDto.SF1);

        dto.setLocation(carParkLocationService.getByIdDto(dto.getPId()));
        dto.setImgList(Optional.ofNullable(carParkImgService.getListByPidDto(dto.getPId())).orElse(Collections.emptyList()));
        dto.setTime(Optional.ofNullable(carParkTimeService.getUniqueByPId(dto.getPId())).map(carParkTimeService::modelToDto).orElse(null));
        dto.setCharge(carParkChargeService.getByIdDto(dto.getPId()));

        if (StringUtils.hasLength(dto.getParkSpaceType())) {
            if (dto.getParkSpaceType().equals(PandaConstant.PARK_SPACE_TYPE3)) {
                dto.setRevenueList(carParkRevenueService.getListByPidDto(dto.getPId()));
            }
        }
    }

    /**
     * 数据库对象转页面对象
     *
     * @param list 数据库对象
     * @return 页面对象
     */
    public List<CarParkDTO> modelToDtoList(List<CarPark> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::modelToDto).collect(toList());
    }

    /**
     * 页面对象转数据库对象
     *
     * @param list 页面对象
     * @return 数据库对象
     */
    public List<CarPark> dtoToModelList(List<CarParkDTO> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::dtoToModel).collect(toList());
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto 查询参数，不为空的属性会被自动加入
     */
    public QueryWrapper<CarPark> createQueryWrapper(CarParkDTO dto) {
        return createQueryWrapper(dto, "");
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto        查询参数，不为空的属性会被自动加入
     * @param tableAlias 表别名
     */
    public QueryWrapper<CarPark> createQueryWrapper(CarParkDTO dto, String tableAlias) {
        final QueryWrapper<CarPark> queryWrapper = new QueryWrapper<>();
        if (null != dto) {
            if (StringUtils.hasLength(tableAlias)) {
                if (tableAlias.charAt(tableAlias.length() - 1) != '.') {
                    tableAlias += ".";
                }
            }
            if (StringUtils.hasLength(dto.getCode())) {
                queryWrapper.eq(tableAlias + "code", dto.getCode());
            }
            if (StringUtils.hasLength(dto.getName())) {
                queryWrapper.like(tableAlias + "name", dto.getName());
            }
            if (StringUtils.hasLength(dto.getIntroduce())) {
                queryWrapper.eq(tableAlias + "introduce", dto.getIntroduce());
            }
            if (StringUtils.hasLength(dto.getQrCode())) {
                queryWrapper.eq(tableAlias + "qr_code", dto.getQrCode());
            }
            if (StringUtils.hasLength(dto.getConfirmIn())) {
                queryWrapper.eq(tableAlias + "confirm_in", dto.getConfirmIn());
            }
            if (StringUtils.hasLength(dto.getThirdPartyBill())) {
                queryWrapper.eq(tableAlias + "third_party_bill", dto.getThirdPartyBill());
            }
            if (StringUtils.hasLength(dto.getConfirmOut())) {
                queryWrapper.eq(tableAlias + "confirm_out", dto.getConfirmOut());
            }
            if (StringUtils.hasLength(dto.getOperator())) {
                queryWrapper.eq(tableAlias + "operator", dto.getOperator());
            }
            if (StringUtils.hasLength(dto.getOrgId())) {
                queryWrapper.eq(tableAlias + "org_id", dto.getOrgId());
            }
            if (StringUtils.hasLength(dto.getEnable())) {
                queryWrapper.eq(tableAlias + "enable", dto.getEnable());
            }
            if (StringUtils.hasLength(dto.getOwnBusiness())) {
                queryWrapper.eq(tableAlias + "own_business", dto.getOwnBusiness());
            }
            if (StringUtils.hasLength(dto.getGateBrand())) {
                queryWrapper.eq(tableAlias + "gate_brand", dto.getGateBrand());
            }
        }
        queryWrapper.orderByAsc(tableAlias + "enable");
        return queryWrapper;
    }

    public QueryWrapper<CarParkSearch> createQueryWrapper(CarParkSearch dto) {
        final QueryWrapper<CarParkSearch> queryWrapper = new QueryWrapper<>();
        if (null != dto) {
            if (StringUtils.hasLength(dto.getCode())) {
                queryWrapper.eq("car.code", dto.getCode());
            }
            if (StringUtils.hasLength(dto.getName())) {
                queryWrapper.like("car.name", dto.getName());
            }
            if (StringUtils.hasLength(dto.getParkSpaceType())) {
                queryWrapper.eq("car.park_space_type", dto.getParkSpaceType());
            }
            if (StringUtils.hasLength(dto.getCarParkType())) {
                queryWrapper.eq("car.car_park_type", dto.getCarParkType());
            }
            if (StringUtils.hasLength(dto.getThirdPartyBill())) {
                queryWrapper.eq("car.third_party_bill", dto.getThirdPartyBill());
            }
            if (StringUtils.hasLength(dto.getOperator())) {
                queryWrapper.eq("car.operator", dto.getOperator());
            }
            if (StringUtils.hasLength(dto.getOrgId())) {
                queryWrapper.eq("car.org_id", dto.getOrgId());
            }
            if (StringUtils.hasLength(dto.getEnable())) {
                queryWrapper.eq("car.enable", dto.getEnable());
            }
            if (StringUtils.hasLength(dto.getOwnBusiness())) {
                queryWrapper.eq("car.own_business", dto.getOwnBusiness());
            }
            if (StringUtils.hasLength(dto.getParkType())) {
                queryWrapper.eq("car.park_type", dto.getParkType());
            }
            if (StringUtils.hasLength(dto.getAddress())) {
                queryWrapper.like("loc.address", dto.getAddress());
            }
            if (null != dto.getLat() && dto.getLat() > 0) {
                queryWrapper.eq("loc.lat", dto.getLat());
            }
            if (null != dto.getLng() && dto.getLng() > 0) {
                queryWrapper.eq("loc.lng", dto.getLng());
            }
        }
        queryWrapper.orderByAsc("car.park_type");
        queryWrapper.orderByAsc("car.car_park_type");
        queryWrapper.orderByDesc("car.add_time");
        return queryWrapper;
    }

    /**
     * 批量检查主键在数据库中是否存在
     *
     * @param ids 主键集合
     */
    public List<CarPark> checkIds(List<String> ids) {
        if (null == ids || ids.isEmpty()) {
            throw new ApiException("ids不能为空");
        }
        final List<CarPark> list = super.listByIds(ids);
        if (list.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(ids.toArray()));
        }
        if (ids.size() == list.size()) {
            return list;
        }
        List<String> dbIds = list.stream().map(CarPark::getPId).collect(toList());
        final List<String> notExistsList = ids.stream().filter(id -> !dbIds.contains(id)).collect(toList());
        dbIds.clear();
        if (!notExistsList.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(notExistsList.toArray()));
        }
        return list;
    }

    @SneakyThrows
    @Transactional(readOnly = true)
    public void updateToRedis(String pId, Integer freeStatus, Integer total) {
        updateToRedis(pId, freeStatus, total, 0);
    }

    @SneakyThrows
    @Transactional(readOnly = true)
    public void updateToRedis(String pId, Integer freeStatus, Integer total, Integer free) {
        if (null == freeStatus) {
            if (null ==free) {
                //如果为空显示泊位已满
                freeStatus = PandaConstant.FREE_STATUS_FULL;
            } else if(free == 0){
                //如果为0显示泊位充足
                freeStatus = PandaConstant.FREE_STATUS_ENOUGH;
            }else {
                if (total > 0) {
                    if(total.equals(free)){
                        freeStatus = PandaConstant.FREE_STATUS_FULL;
                    }else{
                        double percentage = ((double) free / total) * 100;
                        if (percentage > 70) {
                            freeStatus = PandaConstant.FREE_STATUS_TENSION;
                        } else {
                            freeStatus = PandaConstant.FREE_STATUS_ENOUGH;
                        }
                    }
                } else {
                    freeStatus = PandaConstant.FREE_STATUS_FULL;
                }
            }
        }
        if(log.isDebugEnabled()) {
            log.debug("pid: {},freeStatus: {}", pId, freeStatus);
        }
        final CarParkDTO carParkDTO = getByIdDto(pId);
        if (null == carParkDTO) {
            if(log.isDebugEnabled()) {
                log.debug("updateToRedis,CarParkDTO为空,pId: {}", pId);
            }
        } else {
            if(null !=total){
                carParkDTO.setParkNum(total);
            }
            buildToRedis(carParkDTO, freeStatus, free);
        }
    }

    /**
     * 停车场信息写入redis
     *
     * @param carParkDTO 停车场对象
     */
    @SneakyThrows
    public void buildToRedis(CarParkDTO carParkDTO) {
        if (null == carParkDTO) {
            return;
        }
        if (null == carParkDTO.getLocation()) {
            log.debug("buildToRedis,位置信息为空,不处理。PId: {}", carParkDTO.getPId());
        } else {
            buildToRedis(carParkDTO, PandaConstant.FREE_STATUS_ENOUGH);
        }

    }

    /**
     * 停车场信息写入redis
     *
     * @param carParkDTO 停车场对象
     * @param freeStatus 空闲状态
     */
    @SneakyThrows
    public void buildToRedis(CarParkDTO carParkDTO, Integer freeStatus) {
        buildToRedis(carParkDTO, freeStatus, 0);
    }

    /**
     * 停车场信息写入redis
     *
     * @param carParkDTO 停车场对象
     * @param freeStatus 空闲状态
     */
    @SneakyThrows
    public void buildToRedis(CarParkDTO carParkDTO, Integer freeStatus, Integer freeParkNum) {
//        log.info("carParkDTO: {}", carParkDTO.getLocation());
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID_REDIS, carParkDTO.getPId());
        CarParkInfoDTO carParkInfoDTO = new CarParkInfoDTO();
        carParkInfoDTO.setParkNum(carParkDTO.getParkNum())
                .setFreeParkNum(freeParkNum)
                .setEtcPay(carParkDTO.getEtcPay())
                .setEnable(carParkDTO.getEnable())
                .setAddress(carParkDTO.getLocation().getAddress())
                .setCode(carParkDTO.getCode())
                .setName(carParkDTO.getName())
                .setPId(carParkDTO.getPId())
                .setCarParkType(carParkDTO.getCarParkType())
                .setParkSpaceType(carParkDTO.getParkSpaceType())
                .setParkType(carParkDTO.getParkType())
                .setIsAppointmentSet(null == carParkDTO.getAppointmentSet() ? ConstantDto.SF0 : ConstantDto.SF1)
                .setOnStreet(carParkDTO.getOnStreet())
                .setConfirmIn(carParkDTO.getConfirmIn())
                .setConfirmOut(carParkDTO.getConfirmOut())
                .setContactNumber(carParkDTO.getContactNumber());
        if (null != carParkDTO.getFee()) {
            carParkInfoDTO.setFeeNote(carParkDTO.getFee().getNote());
        }
        if (null != carParkDTO.getTime()) {
            carParkInfoDTO.setStartTime(carParkDTO.getTime().getStartTime())
                    .setEndTime(carParkDTO.getTime().getEndTime());
        }
        if (null != carParkDTO.getLocation()) {
            carParkInfoDTO.setLat(carParkDTO.getLocation().getLat())
                    .setLng(carParkDTO.getLocation().getLng());
        }
        if (null != freeStatus) {
            carParkInfoDTO.setFreeStatus(freeStatus);
        }
        if (carParkDTO.getParkType().equals(PandaConstant.PARK_TYPE_CHARGING_STATION)) {
            final CarParkChargeDTO charge = carParkDTO.getCharge();
            if (null != charge) {
                Map<String, Object> map = new HashMap<>(16);
                map.put("fastChargeNum", charge.getFastChargeNum());
                map.put("fastChargeUse", charge.getFastChargeUse());
                map.put("slowChargeNum", charge.getSlowChargeNum());
                map.put("slowChargeUse", charge.getSlowChargeUse());
                map.put("allDay", charge.getAllDay());
                map.put("freePark", charge.getFreePark());
                map.put("chargeBrand", charge.getChargeBrand());
                //充电站
                carParkInfoDTO.setCharge(map);
            }

        }
        redisTemplateUtil.getStringRedisTemplate().opsForValue().set(key, objectMapper.writeValueAsString(carParkInfoDTO));
    }

    /**
     * 查询停车场信息，精简过的信息
     *
     * @param pId 停车场ID
     * @return 停车场信息
     */
    @SneakyThrows
    @Transactional(readOnly = true)
    public CarParkInfoDTO getCarParkInfoByRedis(String pId) {
        return getCarParkInfoByRedis(pId, 1);
    }

    /**
     * 查询停车场信息，精简过的信息
     *
     * @param pId 停车场ID
     * @return 停车场信息
     */
    @SneakyThrows
    @Transactional(readOnly = true)
    public CarParkInfoDTO getCarParkInfoByRedis(String pId, int i) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID_REDIS, pId);
        String s = redisTemplateUtil.getStringRedisTemplate().opsForValue().get(key);
        if (StringUtils.hasLength(s)) {
            final CarParkInfoDTO carParkInfoDTO = objectMapper.readValue(s, CarParkInfoDTO.class);
            parkCacheService.buildCarParkApp(carParkInfoDTO);
            if (carParkInfoDTO.getParkType().equals(PandaConstant.PARK_TYPE_CHARGING_STATION)) {
                carParkChargeService.build(carParkInfoDTO);
            }
            return carParkInfoDTO;
        } else {
            if (i >= 5) {
                return null;
            }
            final CarParkDTO carParkDTO = getByIdDto(pId);
            if (null != carParkDTO) {
//                log.info("pId： " + pId);
                buildToRedis(carParkDTO);
            }
            return getCarParkInfoByRedis(pId, i + 1);
        }
    }


    /**
     * 从缓存中获取离中心坐标距离xx米的停车场位置信息
     *
     * @param lat   纬度
     * @param lng   经度
     * @param count 需要查询的数量
     * @return 列表
     */
    @SneakyThrows
    public List<CarParkInfoDTO> getCarParkInfoList(Double lat, Double lng, Long count) {
        //半径
        String radius = Optional.ofNullable(systemSetService.getById(Constants.SYSTEM_RADIUS)).map(sys -> sys.getValue().replace("\"", "")).orElse(".");
        log.debug("半径： {}", radius);
        final Circle circle = new Circle(new Point(lng, lat), new Distance(Double.parseDouble(radius), RedisGeoCommands.DistanceUnit.METERS));
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = RedisGeoUtil.geoRadius(RedisTemplateUtil.buildKey(CarParkLocationService.GEO_ID,true), circle, count);
        Map<String, Long> map = new LinkedHashMap<>();
        Optional.of(results.getContent()).ifPresent(l -> {
            for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoLocationGeoResult : l) {
                String name = geoLocationGeoResult.getContent().getName();
                double value = geoLocationGeoResult.getDistance().getValue();
                map.put(name, (long) value);
            }
        });
        if (map.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> ids = new ArrayList<>(map.keySet()).stream().map(s -> RedisTemplateUtil.getRedisCacheKey(PREFIX_ID_REDIS, s)).collect(toList());
        List<String> list = redisTemplateUtil.getStringRedisTemplate().opsForValue().multiGet(ids);
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(s -> {
            try {
                if (StringUtils.hasLength(s)) {
                    CarParkInfoDTO carParkInfoDTO = objectMapper.readValue(s, CarParkInfoDTO.class);
                    Long distance = map.get(carParkInfoDTO.getPId());
                    log.debug("name: {},距离: {}", carParkInfoDTO.getName(), distance);
                    carParkInfoDTO.setDistance(distance);
                    build(carParkInfoDTO);
                    return carParkInfoDTO;
                }
                return null;
            } catch (JsonProcessingException e) {
                throw new ApiException(e);
            }
        }).filter(Objects::nonNull).collect(toList());
    }

    private void build(CarParkInfoDTO carParkInfoDTO) {
        buildParkSpace(carParkInfoDTO);
        if (!StringUtils.hasLength(carParkInfoDTO.getIsAppointmentSet())) {
            parkCacheService.buildCarParkApp(carParkInfoDTO);
        }
        if (carParkInfoDTO.getParkType().equals(PandaConstant.PARK_TYPE_CHARGING_STATION)) {
            carParkChargeService.build(carParkInfoDTO);
        }
        if (carParkInfoDTO.getParkType().equals(PandaConstant.PARK_TYPE_GAS_STATION)) {
            carParkGasStationService.build(carParkInfoDTO);
        }
        if (null == carParkInfoDTO.getImgList()) {
            final List<CarParkImgDTO> list = carParkImgService.getListByPidDto(carParkInfoDTO.getPId());
            if (null == list || list.isEmpty()) {
                carParkInfoDTO.setImgList(Collections.emptyList());
            } else {
                final List<String> images = list.stream().map(CarParkImgDTO::getPicPathDisplay).collect(toList());
                carParkInfoDTO.setImgList(images);
            }
        }
    }

    public void buildParkSpace(CarParkInfoDTO carParkInfoDTO) {
        if (carParkInfoDTO.getParkType().equals(PandaConstant.PARK_TYPE_CAR_PARK)) {
            if (StringUtils.hasLength(carParkInfoDTO.getParkSpaceType())) {
                if (carParkInfoDTO.getParkSpaceType().equals(PandaConstant.PARK_SPACE_TYPE3) || carParkInfoDTO.getParkSpaceType().equals(PandaConstant.PARK_SPACE_TYPE4)) {
                    SpringContextHolder.getBean(ParkSpaceService.class).numberOfRentable(carParkInfoDTO);
                }
            }
        }
    }


    @Transactional(readOnly = true)
    public IPage<CarParkInfoDTO> search(Page<SearchCarParkDTO> iPage, SearchCarParkDTO searchCarParkDTO) {
        if (StringUtils.hasLength(searchCarParkDTO.getParkSpaceType())) {
            String[] parkSpaceTypes = searchCarParkDTO.getParkSpaceType().split(",");
            if (parkSpaceTypes.length > 1) {
                searchCarParkDTO.setParkSpaceType(null);
                searchCarParkDTO.setParkSpaceTypes(parkSpaceTypes);
            }
        }
        IPage<CarParkInfoDTO> page = getBaseMapper().searchPage(iPage, searchCarParkDTO);
        Optional.ofNullable(page.getRecords()).ifPresent(records -> {
            Map<String, CarParkInfoDTO> map = records.stream().collect(Collectors.toMap(CarParkInfoDTO::getPId, carParkInfoDTO -> carParkInfoDTO));
            List<String> keys = map.keySet().stream().map(s -> RedisTemplateUtil.getRedisCacheKey(PREFIX_ID_REDIS, s)).collect(toList());
            List<String> list = redisTemplateUtil.getStringRedisTemplate().opsForValue().multiGet(keys);
            Optional.ofNullable(list).ifPresent(l -> l.forEach(s -> {
                if (StringUtils.hasLength(s)) {
                    try {
                        CarParkInfoDTO cacheCarParkInfo = objectMapper.readValue(s, CarParkInfoDTO.class);
                        CarParkInfoDTO carParkInfoDTO = map.get(cacheCarParkInfo.getPId());
                        carParkInfoDTO.setFreeStatus(cacheCarParkInfo.getFreeStatus());
                        carParkInfoDTO.setFreeParkNum(cacheCarParkInfo.getFreeParkNum());
                        carParkInfoDTO.setFeeNote(cacheCarParkInfo.getFeeNote());
                        carParkInfoDTO.setStartTime(cacheCarParkInfo.getStartTime());
                        carParkInfoDTO.setEndTime(cacheCarParkInfo.getEndTime());
                        carParkInfoDTO.setOnStreet(cacheCarParkInfo.getOnStreet());
                        if (null != cacheCarParkInfo.getCharge()) {
                            carParkInfoDTO.setCharge(cacheCarParkInfo.getCharge());
                        }
                        build(carParkInfoDTO);
                    } catch (JsonProcessingException e) {
                        throw new ApiException(e);
                    }
                }
            }));
            Optional.ofNullable(list).ifPresent(List::clear);
            keys.clear();
        });
        return page;
    }

    private void checkMb(CarParkDTO carParkDTO) {
        if (StringUtils.hasLength(carParkDTO.getConfirmIn()) && MbUtil.isNotExists(carParkDTO.getConfirmIn(), BaseTableEnum.SF)) {
            throw new ApiException("人工确认入场值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getCarParkType()) && MbUtil.isNotExists(carParkDTO.getCarParkType(), BaseTableEnum.CAR_PARK_TYPE)) {
            throw new ApiException("停车场类型值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getThirdPartyBill()) && MbUtil.isNotExists(carParkDTO.getThirdPartyBill(), BaseTableEnum.SF)) {
            throw new ApiException("是否第三方计费值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getConfirmOut()) && MbUtil.isNotExists(carParkDTO.getConfirmOut(), BaseTableEnum.SF)) {
            throw new ApiException("人工确认出场值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getEnable()) && MbUtil.isNotExists(carParkDTO.getEnable(), BaseTableEnum.SF)) {
            throw new ApiException("是否启用值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getParkType()) && MbUtil.isNotExists(carParkDTO.getParkType(), BaseTableEnum.PARK_TYPE)) {
            throw new ApiException("类型值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getOwnBusiness()) && MbUtil.isNotExists(carParkDTO.getOwnBusiness(), BaseTableEnum.SF)) {
            throw new ApiException("是否自营值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getOnStreet()) && MbUtil.isNotExists(carParkDTO.getOnStreet(), BaseTableEnum.SF)) {
            throw new ApiException("是否占到停车值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getParkSpaceType()) && MbUtil.isNotExists(carParkDTO.getParkSpaceType(), BaseTableEnum.PARK_SPACE_TYPE)) {
            throw new ApiException("车位类型值不存在");
        }
        if (StringUtils.hasLength(carParkDTO.getGateBrand()) && MbUtil.isNotExists(carParkDTO.getGateBrand(), BaseTableEnum.GATE_BRAND)) {
            throw new ApiException("道闸类型值不存在");
        }
    }

    @Transactional(readOnly = true)
    public String idToDisplay(String parkId) {
        String display = "";
        if (StringUtils.hasLength(parkId)) {
            CarPark data = getById(parkId);
            display = (null == data ? "" : data.getName());
        }
        return display;
    }

    @Transactional(readOnly = true)
    public List<CarParkDTO> jurisdictionCarPark() {
        final QueryWrapper<CarPark> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("car.p_id",
                "select id from sys_org_cl where parent_id in" +
                        "(select distinct org_id from sys_org_person where person_id = '" + SecurityUtils.getLoginUser().getUserId() + "')");
        final List<CarParkDTO> list = getBaseMapper().jurisdictionCarPark(queryWrapper);
        if (!list.isEmpty()) {
            list.forEach(carParkDTO -> {
                final CarParkAppointmentSetDTO appointmentSet = parkCacheService.getCarParkAppointmentSetService().getByIdDto(carParkDTO.getPId());
                if (null != appointmentSet) {
                    carParkDTO.setAppointmentSet(appointmentSet);
                }
            });
        }
        return list;
    }

    /**
     * 保安开启关闭停车场
     *
     * @param dto 参数
     */
    @Transactional(rollbackFor = Exception.class)
    public void openOrClose(CarParkLogDTO dto) {
        final String parkId = dto.getParkId();
        final CarParkDTO carParkDTO = getByIdDto(dto.getParkId());
        if (null == carParkDTO) {
            throw new ApiException("停车场不存在");
        }
        if (StringUtils.hasLength(dto.getEnable()) && MbUtil.isNotExists(dto.getEnable(), BaseTableEnum.SF)) {
            throw new ApiException("是否启用值不存在");
        }
        final List<CarParkDTO> list = jurisdictionCarPark();
        if (list.isEmpty()) {
            throw new ApiException("没有权限操作");
        }
        final boolean b = list.stream().anyMatch(c -> c.getPId().equals(dto.getParkId()));
        if (!b) {
            throw new ApiException("没有权限操作");
        }
        carParkDTO.setEnable(dto.getEnable());
        carParkDTO.setUpdateTime(LocalDateTime.now());
        update(carParkDTO);

        CarParkLogDTO carParkLogDTO = new CarParkLogDTO();
        carParkLogDTO.setParkId(parkId);
        carParkLogDTO.setName(carParkLogDTO.getName());
        carParkLogDTO.setEnable(dto.getEnable());
        carParkLogDTO.setNote(dto.getEnable().equals(ConstantDto.SF0) ? "关闭" : "开启");
        carParkLogDTO.setType(1);
        carParkLogDTO.setOrgId(SecurityUtils.getLoginUser().getCurrentOrgId());
        carParkLogDTO.setOperator(SecurityUtils.getLoginUser().getUserId());
        carParkLogDTO.setOperatorTime(carParkDTO.getUpdateTime());
        carParkLogService.save(carParkLogDTO);

        carParkLogDTO.freeData();
        carParkDTO.freeData();
    }


}